package javax.per.toolkit.security;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Random;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.per.toolkit.tools.msg.Msg;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

/**
 * Using for encrypt and decrypt data<br/>
 * Support list:<br/>
 * Irreversible algorithm: MD5,SHA,HMAC<br/>
 * Reversible algorithm:DES,DESede,AES,Blowfish,RC2,ARCFOUR<br/>
 * Encoding type:BASE64<br/>
 * @author yl34569
 *
 */

@SuppressWarnings("restriction")
public class DataEncryptor {
	
	private final static String KEY_MD5="MD5"; 
	private final static String KEY_SHA="SHA"; 
	public static enum keyAlgorithmType{HmacMD5,HmacSHA1,HmacSHA256,HmacSHA384,HmacSHA512}
	public static enum encryptType{DES,DESede,AES,Blowfish,RC2,ARCFOUR}
	public static enum PBEAlgorithmType{PBEWithMD5AndDES,PBEWithMD5AndTripleDES,PBEWithSHA1AndDESede,PBEWithSHA1AndRC2_40}
	
	/**
	 * Message Digest algorithm 5(信息摘要算法)<br/>
	 * Irreversible
	 * @param bs byte array to be encrypt
	 * @return byte array encrypted
	 * @throws NoSuchAlgorithmException
	 */
	public static byte[] MD5Encrypt(byte[] bs) throws NoSuchAlgorithmException{
		return new DataEncryptor().encrypt(bs,KEY_MD5);
	}
	
	/**
	 * Secure Hash Algorithm(安全散列算法)<br/>
	 * Irreversible
	 * @param bs byte array to be encrypt
	 * @return byte array encrypted
	 * @throws NoSuchAlgorithmException
	 */
	public static byte[] SHAEncrypt(byte[] bs) throws NoSuchAlgorithmException{
		return new DataEncryptor().encrypt(bs,KEY_SHA);
	}
	
	/**
	 * BASE64 encoding
	 * @deprecated
	 * @param content string to be encrypt
	 * @return byte array encrypted
	 * @throws IOException
	 */
	public static byte[] BASE64Encrypt(String content) throws IOException{
		return (new BASE64Decoder()).decodeBuffer(content);  
	}
	
	/**
	 * BASE64 decoding
	 * @deprecated
	 * @param bs byte array to be decrypt
	 * @return string decrypted
	 * @throws IOException
	 */
	public static String BASE64Decrypt(byte[] bs) throws IOException{
		return (new BASE64Encoder()).encodeBuffer(bs);    
	}

	
	/**
	 * Hash Message Authentication Code(散列消息鉴别码)
	 * @param bs byte array to be encrypt
	 * @param algorithmType the type of algorithm to generate key
	 * @param secretKeyOut contains the secret key
	 * @return byte array encrypted
	 * @throws IOException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeyException
	 */
	public static byte[] HMACEncrypt(byte[] bs,DataEncryptor.keyAlgorithmType algorithmType,StringBuffer secretKeyOut) throws IOException, NoSuchAlgorithmException, InvalidKeyException{
		if(secretKeyOut==null)
			throw new Error(Msg.get("NULLPARM"));
		String key=new DataEncryptor().initHMACKey(algorithmType);
		secretKeyOut.append(key);
		SecretKey secretKey = new SecretKeySpec(BASE64Encrypt(key), algorithmType.toString());
		Mac mac = Mac.getInstance(secretKey.getAlgorithm());
		mac.init(secretKey);
		return mac.doFinal(bs);
	}
	
	/**
	 * Data Encryption Standard(数据加密算法)<br/>
	 * Encrypt
	 * @param bs byte array to be encrypt
	 * @param key the secret key
	 * @return byte array encrypted
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 * @throws IOException
	 * @throws NoSuchPaddingException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 */
	public static byte[] DESEncrypt(byte[] bs,String key) throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, IOException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException{
		return encryptWith(bs,key,encryptType.DES);
	}
	
	/**
	 * Data Encryption Standard(数据加密算法)<br/>
	 * Decrypt
	 * @param bs byte array to be decrypt
	 * @param key the secret key
	 * @return byte array decryptted
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 * @throws IOException
	 * @throws NoSuchPaddingException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 */
	public static byte[] DESDecrypt(byte[] bs,String key) throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, IOException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException{
		return decryptWith(bs,key,encryptType.DES);
	}
	
	/**
	 * Decrypt with specialized encrypt type
	 * <br/>The type list: DES,DESede,AES,Blowfish,RC2,ARCFOUR
	 * @param bs byte array to be decrypt
	 * @param key the secret key
	 * @param type encrypt type
	 * @return byte array decrypted
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 * @throws IOException
	 * @throws NoSuchPaddingException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 */
	public static byte[] decryptWith(byte[] bs,String key,encryptType type) throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, IOException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException{
		Key k = new DataEncryptor().toKeyWith(BASE64Encrypt(key),type);
		Cipher cipher = Cipher.getInstance(type.toString());
		cipher.init(Cipher.DECRYPT_MODE, k);
		return cipher.doFinal(bs);
	}
	
	/**
	 * Decrypt with specialized encrypt type using stream.
	 * Can't responses for closing the input stream.
	 * <br/>The type list: DES,DESede,AES,Blowfish,RC2,ARCFOUR
	 * @param is file Input stream.
	 * @param os file output stream.
	 * @param key the secret key
	 * @param type encrypt type
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 * @throws IOException
	 * @throws NoSuchPaddingException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 */
	public static void decryptWith(InputStream is,OutputStream os,String key,encryptType type) throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, IOException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException{
		Key k = new DataEncryptor().toKeyWith(BASE64Encrypt(key),type);
		Cipher cipher = Cipher.getInstance(type.toString());
		cipher.init(Cipher.DECRYPT_MODE, k);
		CipherOutputStream cos=new CipherOutputStream(os,cipher);
		byte[] buf=new byte[1<<10];
		int len=0;
		while((len=is.read(buf))!=-1){
			cos.write(buf,0,len);
		}
		cos.close();
	}
	
	/**
	 * Encrypt with specialized encrypt type
	 * <br/>The type list: DES,DESede,AES,Blowfish,RC2,ARCFOUR
	 * @param bs byte array to be decrypt
	 * @param key the secret key
	 * @param type encrypt type
	 * @return byte array encrypted
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 * @throws IOException
	 * @throws NoSuchPaddingException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 */
	public static byte[] encryptWith(byte[] bs,String key,encryptType type) throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, IOException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException{
		Key k = new DataEncryptor().toKeyWith(BASE64Encrypt(key),type);
		Cipher cipher = Cipher.getInstance(type.toString());
		cipher.init(Cipher.ENCRYPT_MODE, k);
		return cipher.doFinal(bs); 
	}
	
	
	/**
	 * Encrypt with specialized encrypt type using stream.
	 * Can't responses for closing the input stream.
	 * <br/>The type list: DES,DESede,AES,Blowfish,RC2,ARCFOUR
	 * @param is file Input stream.
	 * @param os file output stream.
	 * @param key the secret key
	 * @param type encrypt type
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 * @throws IOException
	 * @throws NoSuchPaddingException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 */
	public static void encryptWith(InputStream is,OutputStream os,String key,encryptType type) throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, IOException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException{
		Key k = new DataEncryptor().toKeyWith(BASE64Encrypt(key),type);
		Cipher cipher = Cipher.getInstance(type.toString());
		cipher.init(Cipher.ENCRYPT_MODE, k);
		CipherOutputStream cos=new CipherOutputStream(os,cipher);
		byte[] buf=new byte[1<<10];
		int len=0;
		while((len=is.read(buf))!=-1){
			cos.write(buf,0,len);
		}
		cos.close();
	}
	
	
	/**
	 * Random generate DES key
	 * @param seed seed of key
	 * @return the key string
	 * @throws NoSuchAlgorithmException
	 * @throws IOException
	 */
	public static String generateDESKey(String seed) throws NoSuchAlgorithmException, IOException{
		return new DataEncryptor().generateDESKeyCore(seed,encryptType.DES.toString());
	}
	
	/**
	 * Random generate DES key
	 * @return the key string
	 * @throws NoSuchAlgorithmException
	 * @throws IOException
	 */
	public static String generateDESKey() throws NoSuchAlgorithmException, IOException{
		return new DataEncryptor().generateDESKeyCore(null,encryptType.DES.toString());
	}
	
	/**
	 * Random generate DES key with specialized encrypt type
	 * @param type encrypt type
	 * @return the key string
	 * @throws NoSuchAlgorithmException
	 * @throws IOException
	 */
	public static String generateKeyWith(encryptType type) throws NoSuchAlgorithmException, IOException{
		return new DataEncryptor().generateDESKeyCore(null, type.toString());
	}
	
	/**
	 * Random generate DES key with specialized encrypt type
	 * @param seed seed of key
	 * @param type encrypt type
	 * @return the key string
	 * @throws NoSuchAlgorithmException
	 * @throws IOException
	 */
	public static String generateKeyWith(String seed,encryptType type) throws NoSuchAlgorithmException, IOException{
		return new DataEncryptor().generateDESKeyCore(seed, type.toString());
	}
	
	final public static byte[] initPBESalt(){
		byte[] salt = new byte[8];  
        Random random = new Random();  
        random.nextBytes(salt);  
        return salt; 
	}
	
	/**
	 * Convert string to PBE key
	 * @param password password
	 * @param type algorithm type to use 
	 * @return the key
	 * @throws Exception
	 */
	public static Key toPBEKey(String password,PBEAlgorithmType type) throws Exception {  
        PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());  
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(type.toString());  
        SecretKey secretKey = keyFactory.generateSecret(keySpec);  
        return secretKey;  
    }
	
	/**
	 * Password-based encryption(基于密码加密)<br/>
	 * Encrypt
	 * @param data data to be encrypted
	 * @param password the password
	 * @param type algorithm type to use 
	 * @return the encrypted data
	 * @throws Exception
	 */
	public static byte[] PBEEncrypt(byte[] data, String password,PBEAlgorithmType type,byte[] salt)  
    throws Exception {  
		Key key = toPBEKey(password,type);  
		PBEParameterSpec paramSpec = new PBEParameterSpec(salt, 100);  
		Cipher cipher = Cipher.getInstance(type.toString());  
		cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);  
		return cipher.doFinal(data);  
	}  
	
	/**
	 * Password-based encryption(基于密码加密)<br/>
	 * Encrypt
	 * @param data data to be encrypted
	 * @param password the password
	 * @param type algorithm type to use 
	 * @return the encrypted data
	 * @throws Exception
	 */
	public static void PBEEncrypt(InputStream is,OutputStream os, String password,PBEAlgorithmType type,byte[] salt)  
    throws Exception {  
		Key key = toPBEKey(password,type);  
		PBEParameterSpec paramSpec = new PBEParameterSpec(salt, 100);  
		Cipher cipher = Cipher.getInstance(type.toString());  
		cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);  
		CipherOutputStream cos=new CipherOutputStream(os,cipher);
		byte[] buf=new byte[1<<10];
		int len=0;
		while((len=is.read(buf))!=-1){
			cos.write(buf,0,len);
		}
		cos.flush();
		cos.close();
	}  

	/**
	 * Password-based encryption(基于密码加密)<br/>
	 * Decrypt
	 * @param data data to be decrypted
	 * @param password the password
	 * @param type algorithm type to use 
	 * @return the decrypted data
	 * @throws Exception
	 */
	public static byte[] PBEDecrypt(byte[] data, String password,PBEAlgorithmType type,byte[] salt)  
    throws Exception {  
		Key key = toPBEKey(password,type);  
		PBEParameterSpec paramSpec = new PBEParameterSpec(salt, 100);  
		Cipher cipher = Cipher.getInstance(type.toString());  
		cipher.init(Cipher.DECRYPT_MODE, key, paramSpec);  
		return cipher.doFinal(data);  
	}  
	
	/**
	 * Password-based encryption(基于密码加密)<br/>
	 * Decrypt
	 * @param data data to be decrypted
	 * @param password the password
	 * @param type algorithm type to use 
	 * @return the decrypted data
	 * @throws Exception
	 */
	public static void PBEDecrypt(InputStream is,OutputStream os, String password,PBEAlgorithmType type,byte[] salt)  
    throws Exception {  
		Key key = toPBEKey(password,type);  
		PBEParameterSpec paramSpec = new PBEParameterSpec(salt, 100);  
		Cipher cipher = Cipher.getInstance(type.toString());  
		cipher.init(Cipher.DECRYPT_MODE, key, paramSpec);  
		CipherOutputStream cos=new CipherOutputStream(os,cipher);
		byte[] buf=new byte[1<<10];
		int len=0;
		while((len=is.read(buf))!=-1){
			cos.write(buf,0,len);
		}
		cos.flush();
		cos.close();
	}  
	
	/**
	 * Generate DES key
	 * @param seed seed of key
	 * @return the key
	 * @throws IOException
	 * @throws NoSuchAlgorithmException
	 */
	final protected String generateDESKeyCore(String seed,String type) throws IOException, NoSuchAlgorithmException{
		SecureRandom secureRandom = null;
		if (seed != null) {  
            secureRandom = new SecureRandom(BASE64Encrypt(seed));  
        } else {  
            secureRandom = new SecureRandom();  
        } 
		KeyGenerator kg = KeyGenerator.getInstance(type);  
        kg.init(secureRandom);  
        SecretKey secretKey = kg.generateKey();  
        return BASE64Decrypt(secretKey.getEncoded());  
	}
	
	/**
	 * Convert string to DES key
	 * @param key the string
	 * @return the key
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 */
	protected Key toDESKey(byte[] key) throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException{
		DESKeySpec dks = new DESKeySpec(key); 
		SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptType.DES.toString());
		SecretKey secretKey = keyFactory.generateSecret(dks); 
		return secretKey;
	}
	
	/**
	 * Generate key with specialized encrypt type from string
	 * @param key key string
	 * @param type encrypt type
	 * @return the key
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 */
	protected Key toKeyWith(byte[] key,encryptType type) throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException{
		SecretKey secretKey = new SecretKeySpec(key, type.toString());
		return secretKey;
	}

	/**
	 * Get secret key
	 * @param algorithmType the type of algorithm to generate key
	 * @return the secret key
	 * @throws NoSuchAlgorithmException
	 * @throws IOException
	 */
	protected String initHMACKey(DataEncryptor.keyAlgorithmType algorithmType ) throws NoSuchAlgorithmException, IOException{
		KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithmType.toString());
		SecretKey secretKey = keyGenerator.generateKey();
		return BASE64Decrypt(secretKey.getEncoded());
	}
	
	/**
	 * Change byte array to string base on a specialized radix
	 * @param bs byte array 
	 * @param radix the radix
	 * @return formated string
	 */
	public static String toBigIntegerString(byte[] bs,int radix){
		BigInteger bi=new BigInteger(bs);
		return bi.toString(radix);
	}
	
	final protected byte[] encrypt(byte[] bs,String type) throws NoSuchAlgorithmException{
		MessageDigest md5 = MessageDigest.getInstance(type);
		md5.update(bs);
		return md5.digest();
	}
}
